/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.rpc.server;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.iec.edp.caf.commons.exception.CAFRuntimeException;
import io.iec.edp.caf.commons.exception.entity.DefaultExceptionProperties;
import io.iec.edp.caf.commons.exception.entity.ExceptionErrorCode;
import io.iec.edp.caf.commons.exception.ExceptionLevel;
import io.iec.edp.caf.commons.utils.InvokeService;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.rpc.api.entity.RpcServiceMethodDefinition;
import io.iec.edp.caf.rpc.api.serialize.RpcSerializeUtil;
import io.iec.edp.caf.rpc.api.service.InternalServiceManageService;
import io.iec.edp.caf.rpc.api.service.RpcServer;
import io.iec.edp.caf.rpc.api.utils.InternalSvrContainer;
import io.iec.edp.caf.rpc.api.utils.RpcAppContextUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.util.Assert;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;

/**
 * @author Leon Huo
 */
@Slf4j
public class RpcServerImpl implements RpcServer {

    private InternalServiceManageService management;

    public RpcServerImpl(InternalServiceManageService management) {
        this.management = management;
    }

    /**
     * invoke service
     * @param serviceId   service id
     * @param version     service version
     * @param paramDict   service parameters
     * @return  serialized the service id method return value
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    @Override
    public Object invokeService(String serviceId, String version, LinkedHashMap<String, Object> paramDict) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, JsonProcessingException {
        var result = invoke(serviceId,version,paramDict);
        if (result == null) {
            return "";
        }

        RpcServiceMethodDefinition rpcServiceMethodDefinition = this.management.getRpcMethodDefinition(serviceId);

        var start = System.currentTimeMillis();
        var retvalue = RpcSerializeUtil.serializeReturnValue(result, rpcServiceMethodDefinition.getReturnInfo());
        var end = System.currentTimeMillis();
        log.info("rpc remote invoke server serialize time：{} ms",end-start);

        return retvalue;
    }

//    /**
//     * invoke service by return input stream
//     * @param serviceId service id
//     * @param version   service version
//     * @param paramDict service parameters
//     * @return the service id method return value,
//     *         if use this way,the target service id method should return a input stream
//     */
//    @Override
//    public InputStream invokeServiceStream(String serviceId, String version, LinkedHashMap<String, Object> paramDict) {
//        var result = invoke(serviceId,version,paramDict);
//        if (result == null) {
//            return new ByteArrayInputStream(new byte[0]);
//        }
//
//        return result;
//    }

    @SneakyThrows
    private Object invoke(String serviceId, String version, LinkedHashMap<String, Object> paramDict){

        //get RpcDefinition in memory local variable
        RpcServiceMethodDefinition rpcServiceMethodDefinition = this.management.getRpcMethodDefinition(serviceId);
        if (rpcServiceMethodDefinition == null) {
            throw new CAFRuntimeException(DefaultExceptionProperties.SERVICE_UNIT,
                    DefaultExceptionProperties.RESOURCE_FILE,
                    ExceptionErrorCode.serviceNotFound,
                    new String[]{serviceId},
                    null, ExceptionLevel.Error, false);
        }


        Method method;
        Object instance;
        String methodName = rpcServiceMethodDefinition.getId().substring(rpcServiceMethodDefinition.getId().lastIndexOf('.') + 1);
        String interfaceString = rpcServiceMethodDefinition.getParentDefinition().getClassName();
        Class iType;
        try {
            //注意此处需要传入线程持有的classLoader 否则对当前类的classLoader(LaunchedURLClassLoader)来说 SU中的Class将是不可见的
            iType = InvokeService.getClass(interfaceString);
            try{
                instance = SpringBeanUtils.getBean(iType);
            } catch (NoSuchBeanDefinitionException e){
                instance = InternalSvrContainer.getService(iType);
            }

            method = RpcAppContextUtils.getMethodByName(iType, methodName);
            Assert.isTrue(method != null && instance != null);
        } catch (Exception e) {
            throw new CAFRuntimeException(DefaultExceptionProperties.SERVICE_UNIT,
                    DefaultExceptionProperties.RESOURCE_FILE,
                    ExceptionErrorCode.serviceNotFound,
                    new String[]{serviceId},
                    null, ExceptionLevel.Error, false);
        }

        Class type = instance.getClass();
        if (type.getName().contains("$$EnhancerBySpring")) {
            type = type.getSuperclass();
        }

        //反序列化参数
        Object[] paramArray = RpcSerializeUtil.deSerializeParameter(iType, type, paramDict, methodName);

        var start = System.currentTimeMillis();

        //执行方法
        Object result = method.invoke(instance, paramArray);
        var end = System.currentTimeMillis();
        log.info("rpc remote invoke server total time：{} ms",end-start);

        return result;
    }
}
